﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MessageWorkflow.Handler
{
    /// <summary>
    /// Default implementation which bares the responsibility to be synchronous
    /// </summary>
    public class AsyncResult<T> : IAsyncResult
    {
        // Fields set at construction which never change while 
        // asyncResult is pending
        private readonly AsyncCallback _callback;
        private readonly T _state;
        private int _completedState;

        // Field that may or may not get set depending on usage
        private ManualResetEvent _waitHandle;

        // Fields set when asyncResult completes
        private Exception m_exception;

        public AsyncResult(AsyncCallback callback, T state)
        {
            _callback = callback;
            _state = state;
        }

        public void SetAsCompleted(Exception exception, bool completedSynchronously)
        {
            // Passing null for ex means no error occurred. 
            // This is the common case
            m_exception = exception;

            // The _completedState field MUST be set prior calling the callback
            AsyncCompletedState prevState = (AsyncCompletedState)Interlocked.Exchange(ref _completedState, 
                completedSynchronously ? (int)AsyncCompletedState.CompletedSynchronously : 
                (int)AsyncCompletedState.CompletedAsynchronously);

            if (prevState != AsyncCompletedState.Pending)
                throw new InvalidOperationException("You can set a the result only once");

            // If the event exists, set it
            if (_waitHandle != null) 
                _waitHandle.Set();

            // If a callback method was set, call it
            if (_callback != null) 
                _callback(this);
        }

        public void EndInvoke()
        {
            // This method assumes that only 1 thread calls EndInvoke 
            // for this object
            if (!IsCompleted)
            {
                if (_waitHandle != null)
                {
                    // If the asyncResult isn't done, wait for it
                    _waitHandle.WaitOne();
                    _waitHandle.Close();
                    _waitHandle = null;  // Allow early GC
                }
            }

            // Operation is done: if an ex occured, throw it
            if (m_exception != null) 
                throw m_exception;
        }

        public T State { get { return _state; } }

        #region Implementation of IAsyncResult

        public object AsyncState { get { return _state; } }

        public bool CompletedSynchronously
        {
            get
            {
#if SILVERLIGHT
                System.Threading.Thread.MemoryBarrier();
                return (AsyncCompletedState)_completedState == AsyncCompletedState.CompletedSynchronously;
#else
                return (AsyncCompletedState)Thread.VolatileRead(ref _completedState) == AsyncCompletedState.CompletedSynchronously;
#endif // SILVERLIGHT
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", 
            Justification = "ManualResetEvent is disposed when disposing this instance")]
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (_waitHandle == null)
                {
                    bool done = IsCompleted;
                    ManualResetEvent manualResetEvent = new ManualResetEvent(done);
                    
                    if (Interlocked.CompareExchange(ref _waitHandle, manualResetEvent, null) != null)
                    {
                        // Another thread created this object's event
                        // dispose the event we just created
                        manualResetEvent.Dispose();
                    }
                    else
                    {
                        if (!done && IsCompleted)
                        {
                            // If the asyncResult wasn't done when we created 
                            // the event but now it is done, set the event
                            _waitHandle.Set();
                        }
                    }
                    
                }
                return _waitHandle;
            }
        }

        public bool IsCompleted
        {
            get
            {
#if SILVERLIGHT
                System.Threading.Thread.MemoryBarrier();
                return (AsyncCompletedState)_completedState != AsyncCompletedState.Pending;
#else
                return (AsyncCompletedState)Thread.VolatileRead(ref _completedState) != AsyncCompletedState.Pending;
#endif // SILVERLIGHT
            }
        }
        #endregion
    }
}
